Описание проекта:
Необходимо провести оценку результатов A/B-теста, чтобы оценить измененияй, связанные с внедрением улучшенной рекомендательной системы для пользователей интернет-магазина.
Мы располагаем следующими датасетами:
Цель:
оценить корректность проведения результатов A/B-теста и проанализировать его результаты.
Задача: удостовериться, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно; проверить равномерность распределения пользователей по тестовым группам и правильность их формирования.
Описание данных:
Данные находятся в файлах final_ab_events.csv, ab_project_marketing_events.csv, final_ab_new_users.csv, final_ab_participants.csv
Выводы:
В данном проекте построена воронка продаж, исследован путь пользователей до покупки. Проанализированы результаты A/B-теста внедрения улучшенной рекомендательной системы. Проведено сравнение двух групп. Выявлено, что новая рекомендательная система значительно не повлияет на поведение пользователей.
Загрузить данные, изучить общую информацию
Исследовать данные:
- Исследовать необходимость преобразования типов (преобразовать даты, обрезать даты).
- Описать природу пропущенных значений (выяснить почему возникли).
- Исследовать наличие дубликатов.
- Оценить корректность проведения теста.
- Проверить соответствие данных требованиям технического задания. Проверить корректность всех пунктов технического задания.
- Проверить время проведения теста. Убедиться, что оно не совпадает с маркетинговыми и другими активностями.
- Проверить аудиторию теста. Удостовериться, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно. Проверить равномерность распределения по тестовым группам и правильность их формирования.
Провести исследовательский анализ данных:
- Количество событий на пользователя одинаково распределены в выборках?
- Как число событий в выборках распределено по дням?
- Как меняется конверсия в воронке в выборках на разных этапах?
- Какие особенности данных нужно учесть, прежде чем приступать к A/B-тестированию?
Оценить результаты A/B-тестирования
- Что можно сказать про результаты A/В-тестирования?
- Проверьте статистическую разницу долей z-критерием.
import pandas as pd
import datetime as dt
from datetime import datetime, timedelta
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st
import seaborn as sns
import math as mth
from statsmodels.stats.proportion import proportions_ztest
import matplotlib as mpl
from plotly import graph_objects as go
from scipy.stats import shapiro
import plotly.express as px
import re
final_ab_events = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\final_ab_events.csv")
final_ab_events
ab_project_marketing_events = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\ab_project_marketing_events.csv")
ab_project_marketing_events
final_ab_new_users = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\final_ab_new_users.csv")
final_ab_new_users
final_ab_participants = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\final_ab_participants.csv")
final_ab_participants
final_ab_events.info()
ab_project_marketing_events.info()
final_ab_new_users.info()
final_ab_participants.info()
final_ab_events.describe()
ab_project_marketing_events.describe()
final_ab_new_users.describe()
final_ab_participants.describe()
В датасете final_ab_events (действия новых пользователей в период с 7 декабря 2020 по 4 января 2021 года) необходимо привести в соответствие тип данных столбца event_dt. В датасете данные о 440317 новых пользователях. В данных столбца details 377577 пропущенных значений. Необходимо исследовать к какому типу пропуски относятся и зависят ли от данных.
В датасете ab_project_marketing_events (календарь маркетинговых событий на 2020 год) необходимо привести в соответствие типы данных столбцов start_dt, finish_dt. Всего данные о 13 маркетинговых событиях.
В датасете final_ab_new_users (пользователи, зарегистрировавшиеся с 7 до 21 декабря 2020 года) необходимо привести в соответствие тип данных столбца first_date. В датасете данные о 61733 пользователях.
В датасете final_ab_participants (таблица участников тестов) информация о 18268 участниках теста.
final_ab_events.isna().sum()
ab_project_marketing_events.isna().sum()
final_ab_new_users.isna().sum()
final_ab_participants.isna().sum()
final_ab_events_nan = final_ab_events[final_ab_events['details'].isna()]
final_ab_events_nan
final_ab_events_nan['event_name'].unique()
final_ab_events['event_name'].unique()
Пропущенные значения по следующим типам события: product_cart, product_page, login. отсутствуют пропуски в типе событий purchase. В описании данных указано: " столбце details хранятся дополнительные данные о событии. Например, для покупок, purchase, в этом поле хранится стоимость покупки в долларах". Следовательно, по типам событий product_cart, product_page, login нет дополнительной информации, поэтому в датасете пропущеные значения. Пропуски зависят от данных. Относятся к неслучайному типу, поэтому мы их оставляем.
final_ab_events.duplicated().sum()
ab_project_marketing_events.duplicated().sum()
final_ab_new_users.duplicated().sum()
final_ab_participants.duplicated().sum()
В данных не имеется явных дубликатов.
#приводим данные к нужному типу
final_ab_events['event_dt'] = pd.to_datetime(final_ab_events['event_dt'])
ab_project_marketing_events['start_dt'] = pd.to_datetime(ab_project_marketing_events['start_dt'])
ab_project_marketing_events['finish_dt'] = pd.to_datetime(ab_project_marketing_events['finish_dt'])
final_ab_new_users['first_date'] = pd.to_datetime(final_ab_new_users['first_date'])
print(final_ab_events.dtypes)
print(ab_project_marketing_events.dtypes)
print(final_ab_new_users.dtypes)
Теперь типы данных во всех столбцах соответствуют нужным типам данных
#объединяем датасет с новыми пользователями с датасетом с действиями пользователей
df1 = pd.merge(final_ab_new_users, final_ab_events, on='user_id', how='left')
df1
#объединяем новый датасет пользователей и действий с датасетом участников теста
df = pd.merge(df1, final_ab_participants, on='user_id', how='left')
df
df.isna().sum()
3745 пропусков в event_dt и event_name в связи с отсутствием событий у некоторых пользователей. 386368 пропусков в графе details в связи с тем, что у некоторых типов событий нет дополнительной информации. 339588 пропусков в group и ab_test в связи с тем, что не все пользователи попали в тесты. Пропуски неслучайны и зависят от данных, поэтому пока их не трогаем.
В данных не хватает событий с 31 декабря по 4 января. Возможно, из-за выходных дней. С отсутствующими данными ничего сделать не сможем.
df
df_eu = df.query('region == "EU" and "2020-12-07" <= first_date <= "2020-12-21"')
cnt_users_eu = df_eu['user_id'].nunique() #количество пользователей из Европы
cnt_users_eu
df_eu_rst = df_eu[df_eu['ab_test'] == 'recommender_system_test']
cnt_users_eu_rst = df_eu_rst['user_id'].nunique() #количество соответствующих техзаданию пользователей: EU и участники теста
cnt_users_eu_rst
percent = cnt_users_eu_rst * 100 / cnt_users_eu
percent
Выборка примерно соответствует техническому заданию.
df_eu_rst['first_date'].min()
df_eu_rst['first_date'].max()
df_eu_rst['event_dt'].min()
df_eu_rst['event_dt'].max()
df['ab_test'].unique()
df2 = df_eu.groupby('user_id').agg({"ab_test":"nunique"}).query('ab_test > 1').reset_index()
df2.columns = ['user_id', 'cnt']
print(df2.head())
print()
cnt_users_both_test = df2['user_id'].nunique()
print(cnt_users_both_test)
print()
print(round(cnt_users_both_test * 100 / cnt_users_eu_rst, 2))
В оба теста попали 1602 пользователя (23,91%). Значительная доля, поэтому удалить их не можем. Необходимо посчитать количество пользователей теста interface_eu_test из группы В, группу А считать не будем, т.к. это контрольная группа, на нее нет воздействия. Количество пользователей теста interface_eu_test из группы А оставляем
#создаем таблицу пользователей, попавших в оба теста
both_test = df2.merge(final_ab_participants[['user_id', 'group', 'ab_test']], on='user_id', how='left')
both_test
#таблица пользователей группы В теста interface_eu_test, попавших в оба теста
test_iet_b = both_test.query('ab_test == "interface_eu_test" and group == "B"')
cnt_test_iet_b = test_iet_b['user_id'].nunique()
print(cnt_test_iet_b)
print()
print(round((cnt_test_iet_b * 100 / cnt_users_eu_rst), 2))
Количество пользователей группы В теста interface_eu_test, попавших в оба теста, - 783 пользователя, 11,68 %. Проверим распределение пользователей группы В по группам нашего теста.
test_iet_b
#создаем датасет с пользователями группы В другого теста, попавших в оба теста, с разбивкой по группам А и В нашего теста
test_iet_b_with_rst = test_iet_b.merge(both_test[['user_id', 'group', 'ab_test']], on='user_id', how='left')
test_iet_b_with_rst.columns = ['user_id', 'cnt', 'group_iet', 'test2', 'group_rst', 'test1']
test_iet_b_with_rst
test_rst_a_both = test_iet_b_with_rst.query('test1 == "recommender_system_test" and group_rst == "A"')
cnt_test_rst_a_both = test_rst_a_both['user_id'].nunique() #пользователи группы А обоих тестов
print(cnt_test_rst_a_both)
print()
users_eu_rst_a = df_eu_rst[df_eu_rst['group'] == "A"]
cnt_users_eu_rst_a = users_eu_rst_a['user_id'].nunique() #пользователи группы А нашего теста
print(cnt_users_eu_rst_a)
print()
print(round(cnt_test_rst_a_both * 100 / cnt_users_eu_rst_a, 2))
test_rst_b_both = test_iet_b_with_rst.query('test1 == "recommender_system_test" and group_rst == "B"')
cnt_test_rst_b_both = test_rst_b_both['user_id'].nunique() #пользователи группы В обоих тестов
print(cnt_test_rst_b_both)
print()
users_eu_rst_b = df_eu_rst[df_eu_rst['group'] == "B"]
cnt_users_eu_rst_b = users_eu_rst_b['user_id'].nunique() #пользователи группы B нашего теста
print(cnt_users_eu_rst_b)
print()
print(round(cnt_test_rst_b_both * 100 / cnt_users_eu_rst_b, 2))
Разница в распределении пользователей по группам А и В нашего теста менее 1 процента, распределение равномерное, можем оставить пользователей группы B теста interface_eu_test из группы В. Пользователи группы B теста interface_eu_test не окажут сильное влияние на результаты.
ab_project_marketing_events
marketing_events_eu = ab_project_marketing_events.query('start_dt <= "2021-01-04" and finish_dt >= "2020-12-07"')
marketing_events_eu
В Европе проводилась только одна акция, которая могла оказать влияние на действия пользователей.
df_promo = df_eu_rst.query('"2020-12-25" <= event_dt <= "2021-01-03"')
df_promo
df_promo['event_dt'] = df_promo['event_dt'].dt.round('24H')
df_promo
df_promo.pivot_table(
index='event_dt',
values='user_id',
aggfunc='nunique'
).plot(figsize=(15, 8), grid=True)
plt.xlabel('Дата маркетинговой акции')
plt.title('Количество пользователей')
plt.show()
Судя по графику акция оказала влияние, но не сильное, т.к. резких переходов и скачков не было.
df_eu_rst_nan = df_eu_rst[df_eu_rst['event_name'].isna()]
cnt_nan_users = df_eu_rst_nan.groupby('group')['user_id'].nunique()
cnt_nan_users
#количество пользователтей, которые не совершали действий
cnt_nan_users_all = cnt_nan_users[0]+cnt_nan_users[1]
cnt_nan_users_all
#доля пользователей, которые не совершали действий
print(round((cnt_nan_users_all * 100 / cnt_users_eu_rst), 2))
#доля пользователей, которые не совершали действий группы А
print(round((cnt_nan_users[0]*100/cnt_users_eu_rst_a), 2))
#доля пользователей, которые не совершали действий группы А
print(round((cnt_nan_users[1]*100/cnt_users_eu_rst_b), 2))
#удаляем пользователей, которые не совершали действий
df_eu_rst = df_eu_rst.dropna(subset=['event_name', 'event_dt'])
cnt_activ_users = df_eu_rst['user_id'].nunique()
cnt_activ_users
print(round((cnt_activ_users * 100 / cnt_users_eu_rst), 2))
После удаления пользователей, которые не совершали действий, мы потеряли 45,2% данных по пользователям. У нас осталось 54,8% данных.
df_eu_rst
df_eu_rst['event_dt'] = pd.to_datetime(df_eu_rst['event_dt']).dt.date
df_eu_rst['event_dt'] = df_eu_rst['event_dt'].astype('datetime64')
df_eu_rst
#ищем возраст событий
df_eu_rst['cnt_days'] = df_eu_rst['event_dt'] - df_eu_rst['first_date']
df_eu_rst.dtypes
#количество пользователей по дням совершения действий
cnt_days = df_eu_rst.groupby('cnt_days')['user_id'].nunique()
cnt_days
cnt_days.plot(kind='bar', figsize=(15, 8), grid=True)
plt.xlabel('Порядок дней')
plt.title('Количество пользователей по дням совершения действий')
plt.show()
df_eu_rst['cnt_days'] = df_eu_rst['cnt_days'].astype('timedelta64[D]')
df_eu_rst['cnt_days'] = df_eu_rst['cnt_days'].astype('int64')
data = df_eu_rst.query('cnt_days <= 14')
data
cnt_days = data.groupby('cnt_days')['user_id'].nunique()
display(cnt_days)
cnt_days.plot(kind='bar', figsize=(15, 8), grid=True)
plt.xlabel('Порядок дней')
plt.title('Количество пользователей по дням совершения действий')
plt.show()
Основное количество пользователей совершали действия в первые дни.
#проверка на пересечение пользоваталей внутри теста
data.groupby('user_id').agg({'group': 'nunique'}).query('group > 1')
В оба теста попали 1602 пользователя (23,91%). Значительная доля, поэтому удалить их не можем. Необходимо посчитать количество пользователей теста interface_eu_test из группы В, группу А считать не будем, т.к. это контрольная группа, на нее нет воздействия. Количество пользователей теста interface_eu_test из группы А оставляем
Количество пользователей группы В теста interface_eu_test, попавших в оба теста, - 783 пользователя, 11,68 %. Проверим распределение пользователей группы В по группам нашего теста.
Разница в распределении пользователей по группам А и В нашего теста менее 1 процента, распределение равномерное, можем оставить пользователей группы B теста interface_eu_test из группы В. Пользователи группы B теста interface_eu_test не окажут сильное влияние на результаты.
Маркетинговая акция не оказала сильное влияние на тест.
Основное количество пользователей совершали действия в первые дни.
cnt_event = data.groupby('group')['event_name'].count().reset_index()
cnt_users = data.groupby('group')['user_id'].nunique().reset_index()
print(cnt_event)
print(cnt_users)
cnt_event['one_user'] = round((cnt_event['event_name'] / cnt_users['user_id']), 0)
print(cnt_event)
В группе А на 1 пользователя приходится 7 событий, в группе В на одного пользователя 6 событий. Распределение среднего количества событий на пользователя в выборках отличается на 16%.
data
reg = data.groupby(['first_date', 'group'])['user_id'].nunique().reset_index()
reg.columns = ['first_date', 'group', 'cnt']
reg
reg['first_date'] = reg['first_date'].dt.date
reg.groupby('group')['cnt'].sum()
ax = sns.barplot(x='first_date',
y='cnt',
hue="group",
data=reg
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Динамика количества пользователей по дням регистрации в разрезе групп')
ax.set(xlabel='Дата регистрации', ylabel='Количество пользователей');
Основное количество пользователей группы А регистрировались с 14 по 21 декабря. Максимальное количество пользователей группы В регистрировались 7 декабря..Пользователи регистрировались неравномерно. Пользователей группы В зарегистрировалось почти в 3 раза меньше.
cnt_events_days = data.groupby(['cnt_days', 'group'])['event_name'].count().reset_index()
cnt_events_days
cnt_events_days.groupby('group')['event_name'].sum()
ax = sns.barplot(x='cnt_days',
y='event_name',
hue="group",
data=cnt_events_days
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Динамика количества событий по дням в разрезе групп')
ax.set(xlabel='Порядковый номер дня', ylabel='Количество событий');
Основное количество событий совершались в первые дни по двум группам. События пользователями группы В совершались в 3,7 раза меньше, чем пользователями группы А
mean_sum = data.groupby('group')['details'].sum().reset_index()
mean_sum
mean_sum.plot(x='group', y='details', kind='bar', figsize=(10, 4), grid=True)
plt.xlabel('Группы')
plt.title('Средняя сумма, потраченная пользователями по группам')
plt.show()
Средняя сумма, потраченная пользователями группы В, в 4 раза меньше потраченной средней суммы группы А.
data['device'].unique()
data
cnt_device = data.groupby(['device', 'group'])['user_id'].nunique().reset_index()
cnt_device
ax = sns.barplot(x='device',
y='user_id',
hue="group",
data=cnt_device
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Распределение пользователей групп по устройствам')
ax.set(xlabel='Устройство', ylabel='Количество пользователей');
Пользователи теста в основном пользуются Android.
Пользователей группы В, использующих Android, в 2,8 раза меньше чем группы А;
пользователей группы В, использующих Mac, в 3,5 раза меньше чем группы А;
пользователей группы В, использующих PC, в 3,2 раза меньше чем группы А;
пользователей группы В, использующих iPhone, в 2,8 раза меньше чем группы А.
Одинаковое распределение пользователей использующих Android и iPhone.
#количество событий по дням
type_events_cnt_days = data.groupby(['cnt_days', 'event_name'])['user_id'].nunique().reset_index()
type_events_cnt_days
ax = sns.barplot(x='cnt_days',
y='user_id',
hue="event_name",
data=type_events_cnt_days
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Динамика событий по дням')
ax.set(xlabel='Порядковый номер дня', ylabel='Количество пользователей');
cnt_users_events_groups = data.pivot_table(index='event_name', columns='group', values='user_id', aggfunc='nunique').reset_index()
cnt_users_events_groups = cnt_users_events_groups.reindex([0,2,1,3])
cnt_users_events_groups
from plotly import graph_objects as go
fig = go.Figure()
fig.add_trace(go.Funnel(
name = 'A',
y = ["login", "product_page", "product_cart", "purchase"],
x = [2747, 1780, 824, 872],
textinfo = "value+percent initial"))
fig.add_trace(go.Funnel(
name = 'B',
orientation = "h",
y = ["login", "product_page", "product_cart", "purchase"],
x = [927, 523, 255, 256],
textposition = "inside",
textinfo = "value+percent initial"))
fig.show()
По группе А мы при первом переходе потеряли 35% пользователей, по группе В - 44% потеряли. При первом шаге на 9 % больше пользователей теряем в группе В.
Во втором переходе по группе А мы потеряли 70% от первого шага, по группе В - 72% потеряли. При втором шаге на 2% больше теряем по группе В.
В третьем переходе по группе А теряем 68% от первого шага (скорее всего, у пользователей есть возможность пропустить предыдущий шаг), по группе В - 72% потеряли. При 3 шаге на 2 процента больше теряем в группе В.
У нас отсутствуют данные с 31 декабря 2020 года по 4 января 2021 года.
Мы удалили значительную долю пользователей, которые не совершали события.
В оба теста попали 1602 пользователя (23,91%). Значительная доля, поэтому удалить их не можем. Необходимо посчитать количество пользователей теста interface_eu_test из группы В, группу А считать не будем, т.к. это контрольная группа, на нее нет воздействия. Количество пользователей теста interface_eu_test из группы А оставляем.
Количество пользователей группы В теста interface_eu_test, попавших в оба теста, - 783 пользователя, 11,68 %.
Разница в распределении пользователей по группам А и В нашего теста менее 1 процента, распределение равномерное, можем оставить пользователей группы B теста interface_eu_test из группы В. Пользователи группы B теста interface_eu_test не окажут сильное влияние на результаты.
В Европе проводилась только одна акция, которая совпала с днями проведения теста.
По группе А мы при первом переходе потеряли 35% пользователей, по группе В - 44% потеряли. При первом шаге на 9 % больше пользователей теряем в группе В. Во втором переходе по группе А мы потеряли 70% от первого шага, по группе В - 72% потеряли. При втором шаге на 2% больше теряем по группе В. В третьем переходе по группе А теряем 68% от первого шага (скорее всего, у пользователей есть возможность пропустить предыдущий шаг), по группе В - 72% потеряли. При 3 шаге на 2 процента больше теряем в группе В.
За 14 дней с момента регистрации пользователи не показали улучшение каждой метрики не менее, чем на 10%
Нулевая гипотеза:
Между группой А и группой В нет значимой разницы при регистрации.
Между группой А и группой В нет значимой разницы при просмотре карточки товара.
Между группой А и группой В нет значимой разницы при просмотре страницы товара.
Между группой А и группой В нет значимой разницы при покупке.
Альтернативная гипотеза:
Между группой А и группой В есть значимая разница при регистрации.
Между группой А и группой В есть значимая разница при просмотре карточки товара.
Между группой А и группой В есть значимая разница при просмотре страницы товара.
Между группой А и группой В есть значимая разница при покупке.
data
cnt_users_events_groups = data.pivot_table(index='event_name', columns='group', values='user_id', margins=True, aggfunc='nunique').reset_index()
cnt_users_events_groups
cnt_users_events_groups = cnt_users_events_groups.rename(columns = {'A': 'group_a', 'B': 'group_b'})
print(cnt_users_events_groups)
df_ab = data.groupby('group').agg({'user_id':'nunique'}).reset_index()
df_ab = (df_ab.assign(idx=df_ab.groupby('group').cumcount())
.pivot_table(index='idx', columns='group',
values='user_id', aggfunc='sum')).reset_index(drop=True)
df_ab = df_ab.rename(columns = {'A': 'group_a', 'B': 'group_b'})
df_ab['all'] = df_ab['group_a']+df_ab['group_b']
df_ab
def z_test(gA, gB, alpha):
for n in cnt_users_events_groups.index:
alpha = 0.05
p1 = cnt_users_events_groups[gA][n] / df_ab[gA]
p2 = cnt_users_events_groups[gB][n] / df_ab[gB]
print(df_ab[gA], df_ab[gB])
p_combined = (cnt_users_events_groups[gA][n] + cnt_users_events_groups[gB][n]) / (df_ab[gA] + df_ab[gB])
difference = p1 - p2
z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/df_ab[gA] + 1/df_ab[gB]))
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
print('{} p-значение: {}'.format(cnt_users_events_groups['event_name'][n], p_value))
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между долями есть значимая разница')
else:
print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными')
print()
z_test('group_a', 'group_b', 0.05)
По результатам z-теста между группой А и группой В нет значимой разницы при регистрации.
Между группой А и группой В нет значимой разницы при просмотре карточки товара.
Между группой А и группой В есть значимая разница при просмотре страницы товара.
Между группой А и группой В есть значимая разница при покупке.
Основное количество пользователей совершали действия в первые дни.
В группе А на 1 пользователя приходится 7 событий, в группе В на одного пользователя 6 событий. Распределение среднего количества событий на пользователя в выборках отличается на 16%.
Основное количество пользователей группы А регистрировались с 14 по 21 декабря. Максимальное количество пользователей группы В регистрировались 7 декабря..Пользователи регистрировались неравномерно. Пользователей группы В зарегистрировалось почти в 3 раза меньше.
Основное количество событий совершались в первые дни по двум группам. События пользователями группы В совершались в 3,7 раза меньше, чем пользователями группы А
Средняя сумма, потраченная пользователями группы В, в 4 раза меньше потраченной средней суммы группы А.
Пользователи теста в основном пользуются Android. Пользователей группы В, использующих Android, в 2,8 раза меньше чем группы А; пользователей группы В, использующих Mac, в 3,5 раза меньше чем группы А; пользователей группы В, использующих PC, в 3,2 раза меньше чем группы А; пользователей группы В, использующих iPhone, в 2,8 раза меньше чем группы А. Одинаковое распределение пользователей использующих Android и iPhone.
На мой взгляд, проведение теста некорректно в связи с тем, что: 1) Отсутствуют данные событий с 31 декабря по 4 января; 2) Период проведения акции выпадает на предновогодние праздники; 3) После исключения пользователей, которые не совершали действий, мы потеряли 45,2% данных по пользователям. У нас осталось 54,8% данных.
Не засчитываем, потому что он проводился в неподходящее время. Параллельно проходил другой тест
data['user_id'].nunique()